﻿using System.Security.Cryptography;

namespace Ofs.Shared
{
    public sealed class DirectoryScanner
    {
        public IFileHashAlgorithm DefaultHashAlgorithm { get; }

        public DirectoryScanner(IFileHashAlgorithm? defaultHashAlgorithm = null)
        {
            this.DefaultHashAlgorithm = defaultHashAlgorithm ?? new FileHashAlgorithm(SHA384.Create);
        }

        private async Task<(string hash, string path)> ScanSingleAsync(
            FileInfo file,
            IFileHashAlgorithm hashAlgorithm,
            CancellationToken cancellationToken)
        {
            NewFileScanning?.Invoke(this, new NewFileScanningEventArgs() {
                ScanningFile = file
            });
            var hash = await hashAlgorithm.ComputeHashAsync(file, cancellationToken);
            return (hash, file.FullName);
        }

        public async ValueTask<ScanResult> ScanAsync(
            DirectoryInfo directory, IFileHashAlgorithm? hashAlgorithm = null,
            CancellationToken cancellationToken = default)
        {
            hashAlgorithm ??= DefaultHashAlgorithm;

            Dictionary<string, List<string>> result = new();

            var tasks = new List<Task<(string hash, string path)>>(10);
            foreach (var file in directory.EnumerateFiles("*", SearchOption.AllDirectories))
            {
                tasks.Add(ScanSingleAsync(file, hashAlgorithm, cancellationToken));
                if (tasks.Count == 10)
                {
                    foreach (var (hash, path) in await Task.WhenAll(tasks))
                    {
                        if (!result.ContainsKey(hash))
                            result.Add(hash, new());
                        result[hash].Add(path);
                    }
                    tasks.Clear();
                }
            }
            foreach (var (hash, path) in await Task.WhenAll(tasks))
            {
                if (!result.ContainsKey(hash))
                    result.Add(hash, new());
                result[hash].Add(path);
            }

            return new ScanResult(directory.FullName, result);
        }

        public event EventHandler<NewFileScanningEventArgs>? NewFileScanning;
    }
}
